package com.beta.cat.application;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.beta.cat.annotation.ExcludeInterceptor;
import com.beta.cat.service.ServiceHealthChecker;
import com.beta.cat.utils.DateUtil;
import com.beta.cat.utils.LogCollectionUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.management.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.Socket;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.jar.JarFile;

@Slf4j
@Controller
@RequestMapping("/")
public class BaseApplication {

    @Autowired(required = false)
    private ConfigurableEnvironment env;
    @Autowired(required = false)
    private ServiceHealthChecker healthChecker;

    private static final LocalDateTime startTime = LocalDateTime.now();

    //是否需要重启
    private static boolean needRestart = false;

    //是否立即重启
    private static boolean restartNow = false;

    //检测间隔 单位:分钟
    @Value("${base.health.checkInterval:5}")
    private int checkInterval;

    //随机检测分钟数 单位:分钟 范围:0~checkInterval-1
    @Value("${base.health.checkMinute:0}")
    private int checkMinute;

    //重启间隔 单位:天，正常情况下，java项目不需要重启，这个时间放长一点
    @Value("${base.health.restartInterval:365}")
    private int restartInterval;

    //重启时间范围:开始时间
    @Value("${base.health.restartStartTime:00:00}")
    private String restartStartTime;

    //重启时间范围:结束时间
    @Value("${base.health.restartEndTime:00:30}")
    private String restartEndTime;

    /**
     * 健康检查，主要是给k8s用的
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/health")
    @ResponseBody
    public String health() throws IOException {
        HttpServletResponse response = null;
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            response = ((ServletRequestAttributes) requestAttributes).getResponse();
        }
        //1、校验是否立即重启,true=立即重启
        if (restartNow) {
            if (response != null) {
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "服务不可用");
                return "服务不可用";
            } else {
                throw new RuntimeException("服务不可用");
            }
        }

        //2、校验当前时间是否在检查时间范围内,不在范围内则跳过检查
        if (LocalTime.now().getMinute() % checkInterval != checkMinute) {
            log.info("当前时间:{}不在检查范围:{}", LocalDateTime.now().getMinute(), checkMinute);
            return "UP";
        }
        // 2.1 判断业务是否正常
        if (healthChecker != null && !healthChecker.isHealthy()) {//业务系统不健康时，要求重启
            if (response != null) {
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "服务不可用");
                return "服务不可用";
            } else {
                throw new RuntimeException("服务不可用");
            }
        }

        //3、校验是否需要重启
        if (!needRestart) {
            if (Duration.between(startTime, LocalDateTime.now()).toDays() > restartInterval) {
                needRestart = true;
            } else {
                return "UP";
            }
        }

        //4、判断当前时间是否在允许重启时间范围内
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
        LocalTime startTime = LocalTime.parse(restartStartTime, formatter);
        LocalTime endTime = LocalTime.parse(restartEndTime, formatter);
        LocalTime now = LocalTime.now();
        if (!now.isBefore(startTime) && now.isBefore(endTime)) {
            restartNow = true;
            if (response != null) {
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "服务不可用");
                return "服务不可用";
            } else {
                throw new RuntimeException("服务不可用");
            }
        } else {
            return "UP";
        }
    }

    /**
     * 基础信息接口，用于判断服务是否启动成功之类的。
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/index")
    @ResponseBody
    public Map index() {
        JSONObject obj = new JSONObject();
        obj.put("current.date", DateUtil.currentDate());
        obj.put("application.name", env == null ? "" : env.getProperty("spring.application.name"));
        obj.put("active.profiles", env == null ? "" : env.getActiveProfiles());
        obj.put("server.port", env == null ? "" : env.getProperty("server.port"));
        obj.put("server.startTime", startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        Map map = new HashMap();
        map.put("code", 200);
        map.put("msg", "SUCCESS");
        map.put("data", obj);
        return map;
    }

    /**
     * 查看当前系统的配置信息（本地+配置中心）。
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/env")
    @ResponseBody
    public Map<String, Object> envContent() {
        if (env == null) {
            return null;
        }
        MutablePropertySources mutablePropertySources = env.getPropertySources();
        if (mutablePropertySources == null) {
            return null;
        }
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("current.date", DateUtil.currentDate());
        map.put("application.name", env == null ? "" : env.getProperty("spring.application.name"));
        map.put("server.startTime", startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        mutablePropertySources.forEach(propertySource -> {//搜集所有的配置信息
            if ((propertySource instanceof BootstrapPropertySource) || (propertySource instanceof OriginTrackedMapPropertySource)) {
                Object source = propertySource.getSource();
                if (!(source instanceof Map)) {
                    return;
                }
                if (CollectionUtils.isEmpty((Map) source)) {
                    return;
                }
                map.putAll((Map) source);
            }
        });
        //重新获取里面的值
        if (map.size() > 0) {
            map.forEach((key, value) -> {
                map.put(key, env.getProperty(key));
            });
        }
        return map;
    }

    /**
     * 验证当前服务到指定的服务器的网络是否畅通
     *
     * @param host 目标服务器
     * @param port 端口
     * @return true-网络通畅；false-网络不通
     */
    @ExcludeInterceptor
    @RequestMapping("/verifyNetwork")
    @ResponseBody
    public boolean verifyNetwork(String host, int port) {
        try (Socket socket = new Socket(host, port)) {
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    /**
     * 应用监控接口，查看当前系统的资源使用情况，如：内存、CPU、jar包的启动时间和打包时间等。
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/monitor")
    @ResponseBody
    public Map monitor() {
        JSONObject obj = new JSONObject();
        obj.put("current.date", DateUtil.currentDate());
        obj.put("application.name", env == null ? "" : env.getProperty("spring.application.name"));
        obj.put("active.profiles", env == null ? "" : env.getActiveProfiles());
        obj.put("server.port", env == null ? "" : env.getProperty("server.port"));
        try {
            obj.putAll(mem());
        } catch (Exception ignore) {
        }
        Map map = new HashMap();
        map.put("code", 200);
        map.put("msg", "SUCCESS");
        map.put("data", obj);
        return map;
    }

    public Map mem() {
        DecimalFormat df = new DecimalFormat("#.#");
        Map result = new LinkedHashMap();
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        // 堆内存使用情况
        MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage();
        MemoryUsage noheap = memoryMXBean.getNonHeapMemoryUsage();

        String jvmInitMem = df.format((memoryUsage.getInit() + noheap.getInit()) * 1.0 / 1024 / 1024) + "M";
        String jvmMaxMem = df.format((memoryUsage.getMax() + noheap.getMax()) * 1.0 / 1024 / 1024) + "M";
        String jvmUsedMem = df.format((memoryUsage.getUsed() + noheap.getUsed()) * 1.0 / 1024 / 1024) + "M";
        String jvmCommitMem = df.format((memoryUsage.getCommitted() + noheap.getCommitted()) * 1.0 / 1024 / 1024) + "M";
        result.put("jvmInitMem", jvmInitMem);
        result.put("jvmMaxMem", jvmMaxMem);
        result.put("jvmUsedMem", jvmUsedMem);
        result.put("jvmCommitMem", jvmCommitMem);

        try {
            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
            result.put("appStartTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(runtimeMXBean.getStartTime())));
            result.put("cmdInputArguments", runtimeMXBean.getInputArguments());
            result.put("jvmName", runtimeMXBean.getVmName());
        } catch (Throwable e) {
        }

        //获取springboot的业务jar包的信息
        try {
            String file = Thread.currentThread().getContextClassLoader().getResource("/META-INF/MANIFEST.MF").getPath();
            Date creationDate = null;
            if (file != null && (file.contains(".jar") || file.contains(".war"))) {
                int idx = file.indexOf(".jar");
                if (idx < 0) {
                    idx = file.indexOf(".war");
                }
                String path = file.substring(0, idx + 4);
                path = path.contains(":") ? path.substring(path.indexOf(":") + 1) : path;
                JarFile jarFile = null;
                try {
                    jarFile = new JarFile(path);
                    creationDate = new Date(jarFile.getEntry("META-INF/MANIFEST.MF").getTime());
                } finally {
                    try {
                        jarFile.close();
                    } catch (Exception e) {
                    }
                }
            }
            if (creationDate == null) {
                log.info("没有获取到对应的时间信息，使用spring方式获取！");
                creationDate = new Date(new ClassPathResource("/META-INF/MANIFEST.MF").lastModified());
            }
            if (creationDate != null) {
                result.put("packageTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(creationDate));
            }
        } catch (Exception e) {
        }

        try {
            OperatingSystemMXBean osXbean = ManagementFactory.getOperatingSystemMXBean();
            result.put("systemLoadAvg", osXbean.getSystemLoadAverage());
            String osJson = JSON.toJSONString(osXbean);
            JSONObject jsonObject = JSON.parseObject(osJson);
            result.put("processCpuLoad", new BigDecimal(jsonObject.getDouble("processCpuLoad") * 100).setScale(2, RoundingMode.HALF_UP));//JVM的CPU占用率，百分比
            result.put("systemCpuLoad", new BigDecimal(jsonObject.getDouble("systemCpuLoad") * 100).setScale(2, RoundingMode.HALF_UP));//系统的CPU占用率，百分比
        } catch (Throwable e) {
        }
        try {
            result.put("loadClassCount", ManagementFactory.getClassLoadingMXBean().getTotalLoadedClassCount());
        } catch (Throwable e) {
        }
        try {
            result.put("threadCount", ManagementFactory.getThreadMXBean().getThreadCount());
            long[] deadLockedThreadIds = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
            if (deadLockedThreadIds != null && deadLockedThreadIds.length > 0) {
                result.put("deadLockedThreadIds", deadLockedThreadIds);
                List<ThreadInfo> threads = new ArrayList<>();
                for (long id : deadLockedThreadIds) {
                    try {
                        threads.add(ManagementFactory.getThreadMXBean().getThreadInfo(id));
                    } catch (Exception e) {
                    }
                }
                result.put("deadLockedThreads", threads);
            }
        } catch (Throwable e) {
        }
        return result;
    }

    /**
     * 查看当前应用的最新的N条记录，方便在线调试
     *
     * @param lines
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/logs")
    @ResponseBody
    public List<String> logs(Integer lines) {
        lines = lines == null ? 100 : lines;

        //优化找本地的完整日志，即：console日志
        File logFile = new File(".", "run.log");
        if (!logFile.exists()) {//获取jar包的当前路径
            String file = Thread.currentThread().getContextClassLoader().getResource("/META-INF/MANIFEST.MF").getPath();
            if (file != null && (file.contains(".jar") || file.contains(".war"))) {
                int idx = file.indexOf(".jar");
                if (idx < 0) {
                    idx = file.indexOf(".war");
                }
                String path = file.substring(0, idx + 4);
                path = path.contains(":") ? path.substring(path.indexOf(":") + 1) : path;
                logFile = new File(new File(path).getParentFile(), "run.log");
            }
        }
        if (!logFile.exists()) {
            //如果没有定义，则再找项目中配置的目录
            String logPath = env.getProperty("logPath", "./logger");//这里的配置是结合log4j2.xml里面的定义
            if (StringUtils.isBlank(logPath)) {
                logPath = ".";
            }
            logFile = new File(logPath, "info.log");
        }

        if (!logFile.exists()) {
            return Collections.emptyList();
        }

        return LogCollectionUtil.readLastLines(logFile.toPath(), "UTF-8", lines);
    }

    @ExceptionHandler({Throwable.class})
    public Map throwableHandler(HttpServletRequest req, HttpServletResponse response, Throwable ex) throws Exception {
        log.error(ex.getMessage());
        Map map = new HashMap();
        map.put("code", 1001);
        map.put("msg", "接口访问异常，请联系客服人员！");
        return map;
    }

    @Data
    public static class ResultVO<T> implements Serializable {
        private Integer code;
        private String msg;
        private T data;

        public ResultVO() {
            this.setCode(200);
            this.setMsg("成功");
        }

        public static <T> ResultVO<T> success(T result) {
            ResultVO<T> resultVO = new ResultVO();
            resultVO.setData(result);
            return resultVO;
        }

        public static ResultVO failure(Integer code, String msg) {
            ResultVO resultVO = new ResultVO();
            resultVO.setCode(code);
            resultVO.setMsg(msg);
            return resultVO;
        }
    }
}
